﻿/* System.Compute: a Computation Framework for .NET/Mono
 * Copyright (C) 2011 Ralf "raboni" Boeckhorst
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

using System.Runtime.InteropServices;

namespace System.Compute.OpenCL
{
    public sealed class DeviceBuffer : ICloneable, IDisposable
    {
        delegate void pfnNotify(IntPtr memobj, byte[] user_data);

        public delegate void Callback(DeviceBuffer buffer, byte[] user_data);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern IntPtr clCreateBuffer(IntPtr context, MemoryFlags flags, SizeT size, IntPtr host_ptr, ref ErrorCode errcode_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clRetainMemObject(IntPtr memobj);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clReleaseMemObject(IntPtr memobj);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetMemObjectInfo(IntPtr mem_obj, MemoryObjectInfo param_name, SizeT param_value_size, ref MemoryObjectType param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetMemObjectInfo(IntPtr mem_obj, MemoryObjectInfo param_name, SizeT param_value_size, ref MemoryFlags param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetMemObjectInfo(IntPtr mem_obj, MemoryObjectInfo param_name, SizeT param_value_size, ref SizeT param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetMemObjectInfo(IntPtr mem_obj, MemoryObjectInfo param_name, SizeT param_value_size, ref IntPtr param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetMemObjectInfo(IntPtr mem_obj, MemoryObjectInfo param_name, SizeT param_value_size, ref uint param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clSetMemObjectDestructorCallback(IntPtr memobj, pfnNotify notify, byte[] user_data);

        public MemoryObjectType Type
        {
            get
            {
                MemoryObjectType ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.Type, sizeof(MemoryObjectType), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public MemoryFlags Flags
        {
            get
            {
                MemoryFlags ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.Flags, sizeof(MemoryFlags), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT Size
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.Size, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public IntPtr HostPtr
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.HostPtr, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MapCount
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.MapCount, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint ReferenceCount
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.ReferenceCount, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public Context Context
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.Context, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return new Context(ret);
            }
        }

        public DeviceBuffer AssociatedMemoryBuffer
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.AssociatedMemoryBuffer, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                if (ret == IntPtr.Zero)
                    return null;
                return new DeviceBuffer(ret);
            }
        }

        public SizeT Offset
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetMemObjectInfo(Handle, MemoryObjectInfo.Offset, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        internal IntPtr Handle;

        internal DeviceBuffer(IntPtr handle)
        {
            Handle = handle;
        }
        
        public DeviceBuffer(Context context, MemoryFlags flags, SizeT size)
        {
            flags = (MemoryFlags)((long)flags % 8); // removes Alloc, Use und CopyHostPtr
            ErrorCode res = ErrorCode.Success;
            Handle = clCreateBuffer(context.Handle, flags, size, IntPtr.Zero, ref res);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public DeviceBuffer(Context context, MemoryFlags flags, SizeT size, object initValue)
        {
            flags = (MemoryFlags)((long)flags % 8); // removes Alloc, Use und CopyHostPtr
            ErrorCode res = ErrorCode.Success;
            GCHandle h = GCHandle.Alloc(initValue, GCHandleType.Pinned);
            Handle = clCreateBuffer(context.Handle, flags | (MemoryFlags)32, size, h.AddrOfPinnedObject(), ref res);
            h.Free();
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public void SetDestructorCallback(Callback callback, byte[] user_data)
        {
            pfnNotify notify = (a, b) =>
                {
                    callback(this, b);
                };
            ErrorCode res = clSetMemObjectDestructorCallback(Handle, notify, user_data);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public DeviceBuffer CreateSubBuffer()
        {
            throw new NotImplementedException();
        }

        public object Clone()
        {
            ErrorCode res = clRetainMemObject(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            return new DeviceBuffer(Handle);
        }

        public void Dispose()
        {
            ErrorCode res = clReleaseMemObject(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            Handle = IntPtr.Zero;
        }
    }
}
